package cz.matasek.dbLogger.gui;

import cz.matasek.dbLogger.exception.DatabaseErrorException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import org.w3c.dom.Document;

/**
 * Table model for displaying result of the query.
 *
 * @author Luboš Matásek
 */
public abstract class ResultSetTableModel extends AbstractTableModel {

    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = -505910554935482168L;
    /**
     * Container of the data.
     */
    protected Object[][] data;
    /**
     * Number of the columns in the table.
     */
    protected int columnCount;
    /**
     * Names of the columns.
     */
    protected String[] columnTitles;
    /**
     * Types of the columns.
     */
    private Class<?>[] columnTypes;

    /**
     * Creates a new <code>HistoricalTableModel</code> for the specified
     * query result.
     *
     * @param queryResultSet result set of the historical query
     */
    public ResultSetTableModel(ResultSet queryResultSet) {
        List<Object[]> resultRows = new ArrayList<Object[]>();

        try {
            // one column is added for row numbers
            columnCount = queryResultSet.getMetaData().getColumnCount() + 1;


            // column title consists of column name and its data type
            columnTitles = new String[columnCount];
            for (int i = 1; i < columnCount; i++) {
                columnTitles[i] =
                        queryResultSet.getMetaData().getColumnName(i) + " [" +
                        queryResultSet.getMetaData().getColumnTypeName(i) + "]";
            }


            // sets column types
            columnTypes = new Class<?>[columnCount];
            columnTypes[0] = Integer.class;

            for (int i = 1; i < columnCount; i++) {
                switch (queryResultSet.getMetaData().getColumnType(i)) {
                    case Types.BIGINT:
                        columnTypes[i] = java.math.BigDecimal.class;
                        break;
                    case Types.BOOLEAN:
                        columnTypes[i] = Boolean.class;
                        break;
                    case Types.DATE:
                        columnTypes[i] = java.util.Date.class;
                        break;
                    case Types.DECIMAL:
                    case Types.DOUBLE:
                    case Types.FLOAT:
                        columnTypes[i] = Double.class;
                        break;
                    case Types.INTEGER:
                    case Types.SMALLINT:
                        columnTypes[i] = Integer.class;
                        break;
                    case Types.TIMESTAMP:
                        columnTypes[i] = java.sql.Timestamp.class;
                        break;
                    case Types.VARCHAR:
                    case Types.CHAR:
                    case Types.NVARCHAR:
                    case Types.NCHAR:
                        columnTypes[i] = String.class;
                        break;
                    default:
                        columnTypes[i] = Object.class;
                }
            }


            // retrieves all rows returned by query
            while (queryResultSet.next()) {
                Object[] row = new Object[columnCount];
                row[0] = new Integer(queryResultSet.getRow());

                // retrieves all column values with proper data types
                for (int i = 1; i < columnCount; i++) {
                    switch (queryResultSet.getMetaData().getColumnType(i)) {
                        case Types.BIGINT:
                            row[i] = queryResultSet.getBigDecimal(i);
                            break;
                        case Types.BOOLEAN:
                            row[i] = queryResultSet.getBoolean(i);
                            break;
                        case Types.DATE:
                            row[i] = queryResultSet.getDate(i);
                            break;
                        case Types.DECIMAL:
                        case Types.DOUBLE:
                        case Types.FLOAT:
                            row[i] = queryResultSet.getDouble(i);
                            break;
                        case Types.INTEGER:
                        case Types.SMALLINT:
                            row[i] = queryResultSet.getInt(i);
                            break;
                        case Types.TIMESTAMP:
                            row[i] = queryResultSet.getTimestamp(i);
                            break;
                        default:
                            row[i] = queryResultSet.getString(i);
                    }
                }

                resultRows.add(row);
            }
        } catch (SQLException ex) {
            throw new DatabaseErrorException(ex);
        }


        // saves data into an array
        data = new Object[resultRows.size()][columnCount];

        for (int i = 0; i < resultRows.size(); i++) {
            data[i] = resultRows.get(i);
        }
    }

    /**
     * Returns the number of rows in the model. A JTable uses this method to
     * determine how many rows it should display. This method should be
     * quick, as it is called frequently during rendering.
     *
     * @return the number of rows in the model
     */
    @Override
    public int getRowCount() {
        return data.length;
    }

    /**
     * Returns the number of columns in the model. A JTable uses this method
     * to determine how many columns it should create and display by
     * default.
     *
     * @return the number of columns in the model
     */
    @Override
    public int getColumnCount() {
        return columnCount;
    }

    /**
     * Returns the value for the cell at <code>columnIndex</code> and
     * <code>rowIndex</code>.
     *
     * @param rowIndex the row whose value is to be queried
     * @param columnIndex the column whose value is to be queried
     * @return the value Object at the specified cell
     */
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return data[rowIndex][columnIndex];
    }

    /**
     * Returns the name of the column at <code>columnIndex</code>. This is
     * used to initialize the table's column header name. Note: this name
     * does not need to be unique; two columns in a table can have the same
     * name.
     *
     * @param columnIndex the index of the column
     * @return the name of the column
     */
    @Override
    public String getColumnName(int columnIndex) {
        return columnTitles[columnIndex];
    }

    /**
     * Returns the most specific superclass for all the cell values in the
     * column. This is used by the {@link javax.swing.JTable} to set up
     * a default renderer and editor for the column.
     *
     * @param columnIndex the index of the column
     * @return the common ancestor class of the object values in the model
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (columnTypes[columnIndex] != null) {
            return columnTypes[columnIndex].getClass();
        } else {
            return Object.class;
        }
    }

    public abstract Document getXML();
}